Repository navigation
feat: drop support for python 3.9 and 3.10 - #1915
Conversation
|
No actionable comments were generated in the recent review. Walkthrough
Merge Risk:
|
Python 3.9 and 3.10 are past end of life, and the security fixes for most dependencies with open Dependabot alerts are only released for Python 3.10+ or 3.11+. - Require Python >=3.11 in every package and the FastAPI example, and test 3.11 to 3.14 in CI. - Remove the Python <3.10 deprecation warnings and the version gates for StrEnum and Self. - Drop the strenum dependency from functions and postgrest. - Allow pytest 9 and pytest-asyncio 1.3+ in functions and supabase, so pytest gets its tmpdir fix. - Relock, which collapses the per-Python-version forks onto the patched releases.
Raising the ruff target to py311 makes keep-runtime-typing stop applying, so ruff now rewrites Optional and Union to PEP 604 unions everywhere. All changes are from ruff check --fix, apart from the runtime Union aliases ruff has no fix for, which are converted by hand the same way. ASYNC109 is ignored because it only fires on py311+ and would require removing the public timeout parameters of the realtime channel methods.
d18f511 to
42d8f97
Compare
Follows #1915. ## Summary - Add Python 3.15 to the `ci.yml` test matrix and the FastAPI e2e matrix. - Bump the uv pinned in CI from 0.8.2 to 0.13.3. uv 0.8.2 predates Python 3.15 and cannot install it. - Upgrade the locked packages that had no 3.15 wheels: pydantic 2.14.2 and pydantic-core 2.50.2 (pydantic-core 2.41 cannot build for 3.15 because its PyO3 stops at 3.14), pyiceberg 0.12.0, pyroaring, markupsafe, mmh3 and cffi. - pydantic 2.14 no longer lets mypy infer the type of a module-level `TypeAdapter`, so annotate the five that need it in auth and storage, using the `Name: TypeAdapter[T] = TypeAdapter(T)` pattern already used elsewhere. - Update `AGENTS.md`. pyiceberg 0.12 moves `pyiceberg-core` out of the `pyarrow` extra into its own extra. storage only uses `PyArrowFileIO`, and the storage tests pass. ## Testing - `make mypy` passes on Python 3.11 and 3.15. - On Python 3.15 locally: functions, supabase, postgrest, storage and auth pass. The full infrastructure-backed suites are left to CI.
🤖 I have created a release *beep* *boop* --- ## [2.34.0](v2.33.0...v2.34.0) (2026-11-03) ### Features * drop support for python 3.9 and 3.10 ([#1915](#1915)) ([37786ed](37786ed)) * **examples:** add FastAPI Realtime Rule Engine example ([#1519](#1519)) ([6449e1e](6449e1e)) * **postgrest:** add client-level retry toggle ([#1900](#1900)) ([e1916b5](e1916b5)), closes [#1561](#1561) * **postgrest:** add strip_nulls() ([#1760](#1760)) ([c86ec9f](c86ec9f)) * **realtime:** add explicit REST API call for broadcast ([#1256](#1256)) ([deec947](deec947)) ### Bug Fixes * **auth:** accept oauth clients without a name ([#1830](#1830)) ([936458e](936458e)) * **auth:** accept pending secure email confirmations ([#1845](#1845)) ([1e0c262](1e0c262)) * **auth:** avoid cancelling the running refresh callback ([#1716](#1716)) ([606e0ac](606e0ac)) * **auth:** copy query_params in link_identity ([#1879](#1879)) ([36e9952](36e9952)) * **auth:** emit PASSWORD_RECOVERY from verify_otp ([#1743](#1743)) ([b571cc2](b571cc2)) * **auth:** emit SIGNED_IN when set_session keeps a valid token ([#1749](#1749)) ([9c092db](9c092db)) * **auth:** handle empty OAuth client lists ([#1736](#1736)) ([7d68e6b](7d68e6b)) * **auth:** keep existing session in sign-up and sign-in methods ([#1935](#1935)) ([c3fa0f1](c3fa0f1)) * **auth:** keep existing session when verify_otp does not return one ([#1933](#1933)) ([cae942d](cae942d)) * **auth:** keep expired session in initialize_from_storage when auto refresh is off ([#1938](#1938)) ([1436832](1436832)) * **auth:** keep or clear the session by scope when sign out fails ([#1824](#1824)) ([de9932e](de9932e)) * **auth:** make code_verifier optional in CodeExchangeParams ([#1922](#1922)) ([0b1d490](0b1d490)) * **auth:** map httpx transport errors to AuthRetryableError ([#1808](#1808)) ([901f00d](901f00d)) * **auth:** normalize trailing slash in base URL ([#1766](#1766)) ([de040ec](de040ec)) * **auth:** preserve phone numbers in MFA factor responses ([#1837](#1837)) ([0e1cf4c](0e1cf4c)) * **auth:** preserve the PKCE recovery event ([#1906](#1906)) ([cd0c4a3](cd0c4a3)) * **auth:** preserve the server error message on retryable 5xx responses ([#1833](#1833)) ([c1727d5](c1727d5)) * **auth:** raise AuthInvalidJwtError for malformed JWTs in get_claims ([#1871](#1871)) ([1aa8090](1aa8090)) * **auth:** raise AuthInvalidJwtError for malformed tokens in set_session ([#1920](#1920)) ([301ce7d](301ce7d)) * **auth:** read implicit grant params from the url fragment ([#1923](#1923)) ([de2c6fc](de2c6fc)) * **auth:** require pyjwt>=2.15.1 for security fixes ([#1820](#1820)) ([f41e80e](f41e80e)) * **auth:** send one refresh request per auto-refresh tick ([#1854](#1854)) ([bc29bc6](bc29bc6)) * **auth:** send PKCE challenges for email and SSO flows ([#1682](#1682)) ([66cd7ee](66cd7ee)) * **auth:** stop the admin API from sharing the client's header dict ([#1645](#1645)) ([6b53131](6b53131)) * **auth:** use inspect.iscoroutinefunction in Timer ([#1875](#1875)) ([4d5d430](4d5d430)) * **auth:** validate session and mfa enroll response after field validation ([#1904](#1904)) ([0310467](0310467)) * **functions:** drop the apiKey header on cross host redirects ([#1885](#1885)) ([268557a](268557a)) * **functions:** keep the query string when invoking a function ([#1816](#1816)) ([685469c](685469c)) * make the capability matrix verifiable ([#1867](#1867)) ([804e7e2](804e7e2)) * **postgrest:** accept generated typeddict types in insert, upsert and update ([#1939](#1939)) ([1ae57b8](1ae57b8)) * **postgrest:** apply normalized timeout to internal httpx clients ([#1758](#1758)) ([774e996](774e996)) * **postgrest:** clear basic auth when selecting bearer auth ([#1713](#1713)) ([32e748b](32e748b)) * **postgrest:** describe non-JSON error responses with the decoded body and a string code ([#1912](#1912)) ([dd75df1](dd75df1)) * **postgrest:** drop pydantic v1 compat layer and align pydantic floor ([#1902](#1902)) ([8ddd531](8ddd531)) * **postgrest:** escape quotes and backslashes in bulk column names ([#1948](#1948)) ([7bebdeb](7bebdeb)) * **postgrest:** json-encode dictionary rpc arguments in get and head requests ([#1899](#1899)) ([86da454](86da454)), closes [#1847](#1847) * **postgrest:** keep the http client and basic auth in schema() ([#1672](#1672)) ([c4e440c](c4e440c)) * **postgrest:** make APIError fields optional so partial error bodies keep their message ([#1732](#1732)) ([18bd628](18bd628)) * **postgrest:** omit columns for empty bulk inserts and upserts ([#1815](#1815)) ([62a4e4d](62a4e4d)) * **postgrest:** preserve chaining after text search ([#1708](#1708)) ([9588283](9588283)) * **postgrest:** preserve injected HTTPX client authentication ([#1838](#1838)) ([79d14d9](79d14d9)) * **postgrest:** quote rpc array elements sent on GET and HEAD ([#1762](#1762)) ([1bb870e](1bb870e)) * **postgrest:** raise on a non-http base_url ([#1881](#1881)) ([610875d](610875d)) * **postgrest:** read the Proxy-Status error code when an error response has no body ([#1621](#1621)) ([34227d1](34227d1)) * **postgrest:** replace conflicting max-affected preferences ([#1827](#1827)) ([5f989e1](5f989e1)) * **postgrest:** retry GET and HEAD requests on network errors ([#1777](#1777)) ([1113f40](1113f40)) * **postgrest:** return no count when the total is unknown ([#1892](#1892)) ([3c21359](3c21359)) * **postgrest:** send rpc list args as array literals on GET and HEAD ([#1752](#1752)) ([50e7a4c](50e7a4c)) * **postgrest:** serialize null and booleans in in_() like other filters ([#1863](#1863)) ([2cc0e03](2cc0e03)) * preserve functions timeout when replacing client options ([#1722](#1722)) ([7fac036](7fac036)) * **realtime:** accept postgres change error arrays ([#1908](#1908)) ([9c59f9a](9c59f9a)) * **realtime:** call late timeout handlers without a payload ([#1831](#1831)) ([b6ddb74](b6ddb74)) * **realtime:** cap reconnect backoff at 60 seconds ([#1768](#1768)) ([23ddbca](23ddbca)) * **realtime:** default postgres_changes schema to public ([#1884](#1884)) ([a6e562d](a6e562d)) * **realtime:** deliver every broadcast to "*" listeners, ignoring case ([#1818](#1818)) ([eae2838](eae2838)) * **realtime:** deprecate channel.join() and route it through subscribe ([#1928](#1928)) ([8ac936c](8ac936c)) * **realtime:** honor the WebSocket opening timeout ([#1839](#1839)) ([46efa11](46efa11)) * **realtime:** keep postgres bindings and report mismatches on join replies ([#1929](#1929)) ([cfb2159](cfb2159)) * **realtime:** keep references to background tasks ([#1888](#1888)) ([f959593](f959593)) * **realtime:** keep the replacement channel when the old one closes ([#1877](#1877)) ([af1d15d](af1d15d)) * **realtime:** notify subscribers of channel errors and closure ([#1846](#1846)) ([bcecfc3](bcecfc3)) * **realtime:** reconnect when the server closes the socket normally ([#1675](#1675)) ([4dafafd](4dafafd)) * **realtime:** redact api key and access tokens from debug logs ([#1750](#1750)) ([17147f2](17147f2)) * **realtime:** rejoin channels on the stepped schedule ([#1916](#1916)) ([db14ec5](db14ec5)) * **realtime:** rejoin the channel after a presence callback is added while joined ([#1686](#1686)) ([697170c](697170c)) * **realtime:** report CHANNEL_ERROR to subscribers when the connection drops ([#1897](#1897)) ([a00d1aa](a00d1aa)) * **realtime:** restart timeout after resending push ([#1654](#1654)) ([da3a353](da3a353)) * **realtime:** serialize initial websocket handshakes ([#1853](#1853)) ([c5150d6](c5150d6)) * **realtime:** use the client timeout for join and leave pushes ([#1685](#1685)) ([198396d](198396d)) * **realtime:** wait for the join reply in subscribe ([#1940](#1940)) ([c725015](c725015)) * **storage:** accept field names in MetadataConfiguration ([#1921](#1921)) ([e0b5716](e0b5716)) * **storage:** allow null metadata in list_v2 ([#1804](#1804)) ([9d2f19c](9d2f19c)) * **storage:** close the upload handle we opened, and only that one ([#1607](#1607)) ([2f3bf40](2f3bf40)) * **storage:** install the iceberg file backend ([#1873](#1873)) ([b2cbf04](b2cbf04)) * **storage:** let upload headers override the client's headers ([#1942](#1942)) ([09855c3](09855c3)) * **storage:** merge a partial sortBy with the default in list ([#1817](#1817)) ([3fb5620](3fb5620)) * **storage:** parse analytics bucket error responses ([#1894](#1894)) ([c5d0149](c5d0149)) * **storage:** read object info in exists() so auth and bucket errors raise ([#1825](#1825)) ([980ac9f](980ac9f)) * **storage:** require topK in vector index query ([#1946](#1946)) ([a60e0d2](a60e0d2)) * **storage:** stop create_client from emitting the timeout deprecation warning ([#1919](#1919)) ([3349ad8](3349ad8)) * **supabase:** keep postgrest and storage timeouts in replace() ([#1809](#1809)) ([795cad4](795cad4)) * **supabase:** keep the session token on every auth event that has one ([#1856](#1856)) ([ca2fb99](ca2fb99)) * **supabase:** reject non-string url and key in create_client ([#1926](#1926)) ([030cbc9](030cbc9)) * **supabase:** replace() preserves postgrest and storage timeouts ([#1840](#1840)) ([772ee24](772ee24)) ### Performance Improvements * **postgrest:** speed up JSON parsing by 22x using json.loads ([#1571](#1571)) ([c56c5bb](c56c5bb)) ### Documentation * add support policy ([#1917](#1917)) ([f1392d4](f1392d4)) * Clarify when get_user() returns None ([#1569](#1569)) ([3d8417f](3d8417f)) * document SDK compatibility with Supabase services ([#1870](#1870)) ([1ff1e4a](1ff1e4a)) --- This PR was generated with [Release Please](https://github.com/googleapis/release-please). See [documentation](https://github.com/googleapis/release-please#release-please). Co-authored-by: supabase-py-releaser[bot] <254608689+supabase-py-releaser[bot]@users.noreply.github.com>
Stacked on #1914.
Summary
Python 3.9 and 3.10 are past end of life, and the security fixes for most dependencies with open Dependabot alerts are only released for Python 3.10+ or 3.11+. Dropping both versions lets the lockfile resolve every package to a single patched version.
The PR has two commits so the hand-made changes can be reviewed separately from the mechanical ruff rewrite.
1. Drop Python 3.9 and 3.10
requires-python = ">=3.11"in every package and in the FastAPI example.StrEnumandSelf.strenumdependency from functions and postgrest.uv.lockresolves to a patched version. pyarrow is held at 26.0.0 because 26.0.1 was published today without most of its wheels.AGENTS.md.2. Ruff pyupgrade fixes for py311
With
target-version = "py311", ruff'skeep-runtime-typingstops applying, soruff check --fixrewritesOptional[X]andUnion[X, Y]toX | NoneandX | Yacross about 100 files,_syncincluded. The runtimeUnionaliases ruff has no fix for were converted by hand the same way.ASYNC109is ignored because it only fires on py311+ and would require removing the publictimeoutparameters on the realtime channel methods.Release note
This uses
feat:rather thanfeat!:so release-please cuts a minor release instead of a major one onmain. Users on 3.9 and 3.10 keep resolving the last compatible release, because pip and uv honourrequires-python.Testing
make mypypasses on Python 3.11, 3.12, 3.13 and 3.14.make <pkg>.testspasses for all six packages on Python 3.11, including the infrastructure-backed suites (functions 121, supabase 50 plus xfail/xpass, postgrest 614, storage 151, auth 257, realtime 89).make build-syncfollowed bymake ruffproduces no diff.Summary by CodeRabbit
Compatibility
Documentation